package com.example.webrtcclient;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.lang.Thread;
import java.net.URL;
import java.net.URLConnection;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.sql.Timestamp;
import java.net.URI;
import java.net.URISyntaxException;

import android.content.Context;
import android.util.Base64;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

import android.util.Log;

import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_17;
import org.java_websocket.handshake.ServerHandshake;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import org.webrtc.DataChannel;
import org.webrtc.DefaultVideoDecoderFactory;
import org.webrtc.DefaultVideoEncoderFactory;
import org.webrtc.EglBase;
import org.webrtc.IceCandidate;
import org.webrtc.Logging;
import org.webrtc.MediaConstraints;
import org.webrtc.MediaStream;
import org.webrtc.PeerConnection;
import org.webrtc.PeerConnectionFactory;
import org.webrtc.RtpReceiver;
import org.webrtc.SdpObserver;
import org.webrtc.SessionDescription;
import org.webrtc.SoftwareVideoDecoderFactory;
import org.webrtc.SoftwareVideoEncoderFactory;
import org.webrtc.VideoDecoderFactory;
import org.webrtc.VideoEncoderFactory;
import org.webrtc.audio.AudioDeviceModule;
import org.webrtc.audio.JavaAudioDeviceModule;
import org.webrtc.audio.JavaAudioDeviceModule.AudioRecordErrorCallback;
import org.webrtc.audio.JavaAudioDeviceModule.AudioTrackErrorCallback;

public class WebRtcClient {
    private final static String TAG = WebRtcClient.class.getCanonicalName();
    private PeerConnectionFactory factory;
    private final EglBase rootEglBase;

    private LinkedList<PeerConnection.IceServer> iceServers = new LinkedList<>();
    private PeerConnectionParameters peerConnectionParameters;
    private RtcListener mListener;
    private WebSocketClient websocketClient;
    private boolean loginInSuccess = false;
    private String userName;
    private String peerName;
    private Peer peer;
    private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
    private final ScheduledExecutorService executorSaveData;
    private final ScheduledExecutorService executorPing;
    private PeerConnection pc;
    private String host;

    private DataChannel dataChannelLocal;
    private long timestampWebrtc = 0;

    private String downloadFileName;
    private long downloadFileSize;
    private long downloadFileSizeCurrent = 0;
    private String downloadSavePath;
    private boolean isDownloadFile = false;

    private boolean isUploadFile = false;
    private boolean uploadFileAccepted = false;
    private String uploadFileName;
    private String uploadFilePath;
    private long uploadFileSize;
    private String uploadRealFileSavePath;

    private boolean createWebrtcCalled = false;
    public boolean hasCallInit() {return createWebrtcCalled;}

    public boolean isLogined() {return loginInSuccess;}
    public boolean isConnected() {
        return websocketClient != null &&
                websocketClient.getReadyState() == WebSocket.READYSTATE.OPEN;
    }

    private long getSystemTimestamp() {
        Timestamp ts = new Timestamp(System.currentTimeMillis());
        return ts.getTime() / 1000;
    }
    private void cleanFileTransformParameter() {
        Log.e(TAG, "cleanFileTransformParameter");
        downloadFileName = "";
        downloadFileSize = 0;
        downloadFileSizeCurrent = 0;
        isDownloadFile = false;
        downloadSavePath = "";

        uploadRealFileSavePath = "";
        isUploadFile = false;
        uploadFileName = "";
        uploadFilePath = "";
        uploadFileAccepted = false;
    }

    public int requestGetFile(String fileName_, String savePath) {
        if(isDownloadFile || isUploadFile) {
            return -1;
        }
        cleanFileTransformParameter();
        isDownloadFile = true;
        this.downloadSavePath = savePath;
        try {
            JSONObject payload = new JSONObject();
            payload.put("fileName", fileName_);
            payload.put("type", "pull");
            sendMessage("file-transform", payload);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return 0;
    }

    public int requestPutFile(String filePath, String fileName) {
        if(isDownloadFile || isUploadFile) {
            return -1;
        }
        cleanFileTransformParameter();
        isUploadFile = true;
        if(filePath.charAt(filePath.length() - 1) == '/') {
            uploadFilePath = filePath;
        } else {
            uploadFilePath = filePath + "/";
        }
        uploadFileName = fileName;
        try {
            File file = new File(uploadFilePath + uploadFileName);
            if (!file.exists()) {
                cleanFileTransformParameter();
                return -1;
            }
            long fileSize = file.length();
            this.uploadFileSize = fileSize;
            JSONObject payload = new JSONObject();
            payload.put("fileName", fileName);
            payload.put("fileSize", fileSize);
            payload.put("type", "push");
            sendMessage("file-transform", payload);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return 0;
    }

    public void cancelFile() {
        if(!isDownloadFile && !isUploadFile) {
            return;
        }

        Log.e(TAG, "cancelFile");
        JSONObject payload = new JSONObject();
        try {
            if(isDownloadFile) {
                payload.put("type", "pull");
            } else {
                payload.put("type", "push");
            }
            sendMessage("file-transform-cancel", payload);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        cleanFileTransformParameter();
        uploadFileAccepted = true;
    }

    /* Implement this interface to be notified of events. */
    public interface RtcListener{
        void onStatusChanged(String newStatus);
        void onAddRemoteStream(MediaStream remoteStream);
        void onRemoveRemoteStream();
        void onPeerLeave();
    }

    private void handleLogin(JSONObject payload) throws JSONException {
        Log.e(TAG, "handleLogin");
        boolean login_result = payload.getBoolean("success");
        if(login_result == true){
            loginInSuccess = true;
            executorPing.execute(new Runnable() {
                @Override
                public void run() {
                    while(loginInSuccess) {
                        try {
                            if(!isConnected()) {break;}
                            final JSONObject payload = new JSONObject();
                            payload.put("type", "ping-signal");
                            if(getSystemTimestamp() - timestampWebrtc > 15) {
                                Log.e(TAG, getSystemTimestamp() + " : " + timestampWebrtc);
                            }
                            executor.execute(new Runnable() {
                                @Override
                                public void run() {
                                    websocketClient.send(payload.toString());
                                }
                            });
                            Thread.sleep(10000);
                        } catch (JSONException e) {
                            e.printStackTrace();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
            Log.e(TAG, "LoginCommand: success");
        } else{
            Log.e(TAG, "Ooops...try a different username");
            sendLoginSignal();
            //loginInSuccess = false;
        }
    }

    private void handleOffer(final JSONObject payload) throws JSONException {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    SessionDescription sdp = new SessionDescription(
                            SessionDescription.Type.fromCanonicalForm(payload.getString("type")),
                            payload.getString("sdp")
                    );
                    pc.setRemoteDescription(peer, sdp);
                    MediaConstraints pcConstraints;
                    pcConstraints = new MediaConstraints();
                    pcConstraints.mandatory.add(
                            new MediaConstraints.KeyValuePair("OfferToReceiveAudio", "false"));
                    pcConstraints.mandatory.add(
                            new MediaConstraints.KeyValuePair("OfferToReceiveVideo", "true"));
                    pcConstraints.optional.add(
                            new MediaConstraints.KeyValuePair("DtlsSrtpKeyAgreement", "true"));
                    pc.createAnswer(peer, pcConstraints);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
        Log.e(TAG,"handleOffer");
    }

    private void handleAnswer(final JSONObject payload) {
        Log.e(TAG,"handleAnswer");
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    SessionDescription sdp = new SessionDescription(
                            SessionDescription.Type.fromCanonicalForm(payload.getString("type")),
                            payload.getString("sdp")
                    );
                    pc.setRemoteDescription(peer, sdp);
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void handleCandidate(final JSONObject payload) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    if (pc.getRemoteDescription() != null) {
                        IceCandidate candidate = new IceCandidate(
                                payload.getString("sdpMid"),
                                payload.getInt("sdpMLineIndex"),
                                payload.getString("candidate"));
                        pc.addIceCandidate(candidate);
                    }
                } catch (JSONException e) {
                    e.printStackTrace();
                }
            }
        });
        Log.e(TAG,"handleCandidate");
    }

    private void handleFileTransform(final JSONObject payload) throws JSONException {
        downloadFileName = payload.getString("fileName");
        downloadFileSize = payload.getLong("fileSize");
        sendMessage("file-transform-parameter-accept", new JSONObject());
    }

    private void handleLeave() {
        mListener.onPeerLeave();
    }

    /**
     * Send a message through the signaling server
     * @param type type of message
     * @param payload payload of message
     */
    public void sendMessage(String type, JSONObject payload) {
        if(!isConnected()) {
            return;
        }
        try {
            final JSONObject message = new JSONObject();
            String to = peerName;
            message.put("name", to);
            message.put("type", type);
            message.put("payload", payload);
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    websocketClient.send(message.toString());
                }
            });
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public boolean sendMessage(String payload) {
        if(!isConnected()) {
            return false;
        }
        try {
            final JSONObject message = new JSONObject();
            String to = peerName;
            message.put("name", to);
            message.put("type", "message");
            message.put("payload", payload);
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    websocketClient.send(message.toString());
                }
            });
            return true;
        } catch (JSONException e) {
            e.printStackTrace();
            return false;
        }
    }

    private void getMessage(JSONObject data){
        try {
            String type = data.getString("type");
            String from = data.optString("from");
            JSONObject payload = data.optJSONObject("payload");
            // if peer is unknown, try to add him
            if(!from.equals("") && (type.equals("offer") || type.equals("candidate"))) {
                while (pc == null) {
                    Thread.sleep(5000);
                    Log.e(TAG,"sleeping pc" + pc);
                }
            }

            switch(type){
            case "login":
                handleLogin(data);
		        break;
            case "query_robot":
                boolean query_robot_result = data.getBoolean("success");
                Log.e(TAG, "query_robot_result " + query_robot_result);
                if(query_robot_result == true) {
                    Log.e(TAG, "stopWebrtc not");
                    payload = new JSONObject();
                    sendMessage("offer-loopback", payload);
                } else {
                    Log.e(TAG, "stopWebrtc 0");
                    stopWebrtc();
                }
                break;
            case "offer-loopback-reconnect":
                if(pc == null) {
                    Log.e(TAG, "pc is null \n");
                }
                stopWebrtc();
                startWebrtc(from);
                sendMessage("start_video_live");
                break;
            case "offer":
                handleOffer(payload);
                break;
            case "answer":
                handleAnswer(payload);
                break;
            case "candidate":
                handleCandidate(payload);
                break;
            case "candidate_list":
                JSONArray candidate_list = data.optJSONArray("payload");
                if(candidate_list != null) {
                    for (int i = 0; i < candidate_list.length(); i++) {
                        handleCandidate(candidate_list.getJSONObject(i));
                    }
                }
                break;
            case "save-data-over":
                uploadFileAccepted = true;
                break;
            case "file-transform-parameter":
                handleFileTransform(payload);
                break;
            case "file-transform-parameter-accept":
                Log.e(TAG, "file-transform-parameter-accept " + dataChannelLocal.state() +
                        isUploadFile + " " );
                if (dataChannelLocal.state() == DataChannel.State.OPEN &&
                        isUploadFile) {
                    Log.e(TAG, "file-transform-parameter-accept sendData");
                    executorSaveData.execute(new Runnable() {
                        @Override
                        public void run() {
                            sendData();
                        }
                    });
                }
                break;
            case "file-transform-file-size-zero":
                cleanFileTransformParameter();
                break;
            case "file-transform-cancel":
                break;
            case "leave":
                handleLeave();
                break;
            case "message":
                break;
            case "pong-signal":
                timestampWebrtc = getSystemTimestamp();
                break;
            case "peer-excced-3":
                break;
            case "peer-not-exist":
                String return_type = data.getString("return_type");
                if(return_type.equals("message")) {
                    String payload_str = data.getString("payload");
                    if(payload_str.equals("start_video_live") ||
                            payload_str.startsWith("video_replay?video=")) {
                        stopWebrtc();
                    }
                } else if(return_type.equals("file-transform")) {
                    stopWebrtc();
                } else {
                    Log.e(TAG, "ignore peer-not-exist");
                }
                break;
            case "toast-info":
                System.out.println(payload.toString());
                System.out.println(payload.optString("content"));
                break;
            default:
                Log.e(TAG, "unkonw message type");
                break;
            }

        } catch (JSONException e) {
            e.printStackTrace();
        } catch(InterruptedException e){
            e.printStackTrace();
        }
    }


    private class Peer implements SdpObserver, PeerConnection.Observer, DataChannel.Observer {
        /* Called on success of Create{Offer,Answer}(). implements SdpObserver interface*/
        @Override
        public void onCreateSuccess(final SessionDescription sdp) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Log.e(TAG, "onCreateSuccess sdp " + "type " + sdp.type.canonicalForm() +
                                " " + sdp.toString());

                        String sdpString = sdp.description;
                        sdpString = preferCodec(
                                sdpString, peerConnectionParameters.videoCodec, false);
                        if (sdpString.equals("")) {
                            JSONObject payload = new JSONObject();
                            sendMessage("leave", payload);
                            Log.e(TAG, "stopWebrtc 1");
                            stopWebrtc();
                            return;
                        }
                        //sdpString = preferCodec(
                        //        sdpString, peerConnectionParameters.audioCodec, true);
                        JSONObject payload = new JSONObject();
                        payload.put("type", sdp.type.canonicalForm());
                        payload.put("sdp", sdpString);
                        sendMessage(sdp.type.canonicalForm(), payload);
                        executor.execute(new Runnable() {
                            @Override
                            public void run() {
                                pc.setLocalDescription(Peer.this, sdp);
                            }
                        });
                    } catch (JSONException e) {
                        e.printStackTrace();
                    }
                }
            });
            Log.e(TAG, "onCreateSuccess");
        }

        @Override
        public void onSetSuccess() {
        }

        @Override
        public void onCreateFailure(String s) {
            Log.e(TAG, "onCreateFailure: " + s);
        }

        @Override
        public void onSetFailure(String s) {
        }


        /* implement PeerConnection.Observer interface */
        @Override
        public void onSignalingChange(PeerConnection.SignalingState signalingState) {
            Log.e(TAG, "onSignalingChange: " + signalingState.toString());
        }

        @Override
        public void onIceConnectionChange(PeerConnection.IceConnectionState iceConnectionState) {
            Log.e(TAG, "onIceConnectionChange " + iceConnectionState.toString());
            if (iceConnectionState == PeerConnection.IceConnectionState.DISCONNECTED) {
                //JSONObject payload = new JSONObject();
                //sendMessage("reserve-connect", payload);
                //setVideoChannelStatus(false);
                //setDataChannelStatus(false);
                //if (websocketClient.getReadyState() != WebSocket.READYSTATE.OPEN) {
                //removePeer();
                //mListener.onPeerLeave();
                //mRecoLiveEvent.onDisconnected();
                //mListener.onStatusChanged("DISCONNECTED");
            }
        }

        @Override
        public void onIceConnectionReceivingChange(boolean var1) {
        }

        @Override
        public void onIceGatheringChange(PeerConnection.IceGatheringState iceGatheringState) {
        }

        @Override
        public void onIceCandidate(final IceCandidate candidate) {
            try {
                JSONObject payload = new JSONObject();
                payload.put("sdpMLineIndex", candidate.sdpMLineIndex);
                payload.put("sdpMid", candidate.sdpMid);
                payload.put("candidate", candidate.sdp);
                sendMessage("candidate", payload);
            } catch (JSONException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onIceCandidatesRemoved(IceCandidate[] var1) {
        }

        ;

        @Override
        public void onAddStream(MediaStream mediaStream) {
            Log.e(TAG, "onAddStream " + mediaStream.toString());
            // remote streams are displayed from 1 to MAX_PEER (0 is localStream)
            mListener.onAddRemoteStream(mediaStream);
        }

        @Override
        public void onRemoveStream(MediaStream mediaStream) {
            Log.e(TAG, "onRemoveStream " + mediaStream.toString());
            //removePeer();
            mListener.onRemoveRemoteStream();
        }

        @Override
        public void onDataChannel(DataChannel dc) {
            dataChannelLocal = dc;
        }

        @Override
        public void onRenegotiationNeeded() {
        }

        @Override
        public void onAddTrack(RtpReceiver var1, MediaStream[] var2) {
        }

        /* implement DataChannel.Observer interface */
        @Override
        public void onBufferedAmountChange(long var1) {
            Log.e(TAG, "onBufferedAmountChange: " + var1);
        }

        @Override
        public void onStateChange() {
            Log.e(TAG, "onStateChange: " + dataChannelLocal.state());
            if (dataChannelLocal.state() == DataChannel.State.OPEN &&
                    isUploadFile ) {
                Log.e(TAG, "sendData");
                executorSaveData.execute(new Runnable() {
                    @Override
                    public void run() {
                        sendData();
                    }
                });
            }
        }

        /**
         * A data buffer was successfully received.  NOTE: |buffer.data| will be
         * freed once this function returns so callers who want to use the data
         * asynchronously must make sure to copy it first.
         */
        @Override
        public void onMessage(DataChannel.Buffer buffer) {
            Log.e(TAG, "onMessage" + buffer.data.capacity());
            if (!isDownloadFile ) {
                return;
            }
            final ByteBuffer clone = ByteBuffer.allocate(buffer.data.capacity());
            buffer.data.rewind();   //copy from the beginning
            clone.put(buffer.data);
            buffer.data.rewind();
            executorSaveData.execute(new Runnable() {
                @Override
                public void run() {
                    saveData(clone);
                }
            });
        }

        public Peer() {
            Log.e(TAG, "new Peer: " + peerName);
            mListener.onStatusChanged("CONNECTING");
        }

        /**
         * Returns the line number containing "m=audio|video", or -1 if no such line exists.
         */
        private int findMediaDescriptionLine(boolean isAudio, String[] sdpLines) {
            final String mediaDescription = isAudio ? "m=audio " : "m=video ";
            for (int i = 0; i < sdpLines.length; ++i) {
                if (sdpLines[i].startsWith(mediaDescription)) {
                    return i;
                }
            }
            return -1;
        }

        private String joinString(
                Iterable<? extends CharSequence> s, String delimiter, boolean delimiterAtEnd) {
            Iterator<? extends CharSequence> iter = s.iterator();
            if (!iter.hasNext()) {
                return "";
            }
            StringBuilder buffer = new StringBuilder(iter.next());
            while (iter.hasNext()) {
                buffer.append(delimiter).append(iter.next());
            }
            if (delimiterAtEnd) {
                buffer.append(delimiter);
            }
            return buffer.toString();
        }

        private String movePayloadTypesToFront(
                List<String> preferredPayloadTypes, String mLine) {
            // The format of the media description line should be: m=<media> <port> <proto> <fmt> ...
            final List<String> origLineParts = Arrays.asList(mLine.split(" "));
            if (origLineParts.size() <= 3) {
                Log.e(TAG, "Wrong SDP media description format: " + mLine);
                return null;
            }
            final List<String> header = origLineParts.subList(0, 3);
            final List<String> unpreferredPayloadTypes =
                    new ArrayList<>(origLineParts.subList(3, origLineParts.size()));
            unpreferredPayloadTypes.removeAll(preferredPayloadTypes);
            // Reconstruct the line with |preferredPayloadTypes| moved to the beginning of the payload
            // types.
            final List<String> newLineParts = new ArrayList<>();
            newLineParts.addAll(header);
            newLineParts.addAll(preferredPayloadTypes);
            newLineParts.addAll(unpreferredPayloadTypes);
            return joinString(newLineParts, " ", false /* delimiterAtEnd */);
        }

        private String preferCodec(String sdpDescription, String codec, boolean isAudio) {
            final String[] lines = sdpDescription.split("\r\n");
            final int mLineIndex = findMediaDescriptionLine(isAudio, lines);
            if (mLineIndex == -1) {
                Log.e(TAG, "preferCodec No mediaDescription line, so can't prefer " + codec);
                return sdpDescription;
            }
            // A list with all the payload types with name |codec|. The payload types are integers in the
            // range 96-127, but they are stored as strings here.
            final List<String> codecPayloadTypes = new ArrayList<>();
            // a=rtpmap:<payload type> <encoding name>/<clock rate> [/<encoding parameters>]
            final Pattern codecPattern = Pattern.compile("^a=rtpmap:(\\d+) " + codec + "(/\\d+)+[\r]?$");
            for (String line : lines) {
                Matcher codecMatcher = codecPattern.matcher(line);
                if (codecMatcher.matches()) {
                    codecPayloadTypes.add(codecMatcher.group(1));
                }
            }

            Log.e(TAG, "preferCodec mLineIndex " + mLineIndex);
            for(int i = 0; i < codecPayloadTypes.size(); i++) {
                Log.e(TAG, "preferCodec " + codecPayloadTypes.get(i));
            }
            if (codecPayloadTypes.isEmpty()) {
                Log.e(TAG, "No payload types with name " + codec);
                return sdpDescription;
            }

            final String newMLine = movePayloadTypesToFront(codecPayloadTypes, lines[mLineIndex]);
            Log.e(TAG, "preferCodec newMLine " + newMLine);
            if (newMLine == null) {
                return sdpDescription;
            }
            Log.d(TAG, "Change media description from: " + lines[mLineIndex] + " to " + newMLine);
            lines[mLineIndex] = newMLine;
            return joinString(Arrays.asList(lines), "\r\n", true /* delimiterAtEnd */);
        }
    }

    private void sendLoginSignal() {
        try {
            final JSONObject payload = new JSONObject();
            payload.put("type", "login");
            payload.put("name", userName);
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    websocketClient.send(payload.toString());
                }
            });
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    private void login() {
        if (!loginInSuccess){
            sendLoginSignal();
        } else {
            Log.e(TAG,"logined alreadly");
        }
    }

    private static String getFieldTrials(PeerConnectionParameters peerConnectionParameters) {
        String VIDEO_FLEXFEC_FIELDTRIAL = "WebRTC-FlexFEC-03-Advertised/Enabled/WebRTC-FlexFEC-03/Enabled/";
        String VIDEO_VP8_INTEL_HW_ENCODER_FIELDTRIAL = "WebRTC-IntelVP8/Enabled/";
        String DISABLE_WEBRTC_AGC_FIELDTRIAL = "WebRTC-Audio-MinimizeResamplingOnMobile/Enabled/";
        String fieldTrials = "";
        if (peerConnectionParameters.videoFlexfecEnabled) {
            fieldTrials += VIDEO_FLEXFEC_FIELDTRIAL;
            Log.e(TAG, "Enable FlexFEC field trial.");
        }
        fieldTrials += VIDEO_VP8_INTEL_HW_ENCODER_FIELDTRIAL;
        if (peerConnectionParameters.disableWebRtcAGCAndHPF) {
            fieldTrials += DISABLE_WEBRTC_AGC_FIELDTRIAL;
            Log.e(TAG, "Disable WebRTC AGC field trial.");
        }
        return fieldTrials;
    }

    public WebRtcClient(Context appContext, EglBase eglBase, PeerConnectionParameters params) {
        // Executor thread is started once in private ctor and is used for all
        // peer connection API calls to ensure new peer connection factory is
        // created on the same thread as previously destroyed factory.
        executorSaveData = Executors.newSingleThreadScheduledExecutor();
        executorPing = Executors.newSingleThreadScheduledExecutor();

        this.rootEglBase = eglBase;
        //this.appContext = appContext;
        this.peerConnectionParameters = params;

        Log.e(TAG, "Initialize WebRTC");
        final String fieldTrials = getFieldTrials(peerConnectionParameters);
        executor.execute(() -> {
            System.err.println("Initialize WebRTC. Field trials: " + fieldTrials);
            PeerConnectionFactory.initialize(
                    PeerConnectionFactory.InitializationOptions.builder(appContext)
                            .setFieldTrials(fieldTrials)
                            .setEnableInternalTracer(true)
                            .createInitializationOptions());
        });
    }

    private void create_websocket(String socketAddress){

        try {
            if (websocketClient == null) {
                websocketClient = new WebSocketClient(new URI(socketAddress), new Draft_17()) {
                    public void onMessage(String message) {
                        System.out.println("got: " + message + "\n");
                        try {
                            JSONObject data = new JSONObject(message);
                            getMessage(data);
                        } catch (JSONException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onOpen(ServerHandshake handshake) {
                        System.out.println("You are connected to ChatServer: " +
                                getURI() + "\n");
                    }

                    @Override
                    public void onClose(int code, String reason, boolean remote) {
                        System.out.println("You have been disconnected from: " + getURI() +
                                "; Code: " + code + " " + reason + "\n");
                    }

                    @Override
                    public void onError(Exception ex) {
                        System.out.println("Exception occured ...\n" + ex + "\n");
                        ex.printStackTrace();
                    }
                };
                websocketClient.connect();
            }
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        try {
            int timeout = 0;
            while(!isConnected() && timeout < 6) {
                Thread.sleep(500);
                timeout =+ 1;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(isConnected()) {
            login();
        }
    }
    public void init(final RtcListener listener, final String socketAddress, String ip, String peerName){
        cleanFileTransformParameter();
        this.host = ip;
        this.peerName = peerName;
        this.mListener = listener;
        peer = new Peer();
        timestampWebrtc = getSystemTimestamp();

        iceServers = new LinkedList<>();

        executor.execute(() ->  {
            if(factory == null) {
                Logging.enableLogToDebugOutput(Logging.Severity.LS_INFO);

                PeerConnectionFactory.Options options = new PeerConnectionFactory.Options();
                if (options != null) {
                    System.err.println("Factory networkIgnoreMask option: " + options.networkIgnoreMask);
                }
                final boolean enableH264HighProfile =
                        "H264 High".equals(peerConnectionParameters.videoCodec);
                final VideoEncoderFactory encoderFactory;
                final VideoDecoderFactory decoderFactory;

                if (peerConnectionParameters.videoCodecHwAcceleration) {
                    encoderFactory = new DefaultVideoEncoderFactory(
                            rootEglBase.getEglBaseContext(), true,
                            enableH264HighProfile);
                    decoderFactory = new DefaultVideoDecoderFactory(rootEglBase.getEglBaseContext());
                } else {
                    encoderFactory = new SoftwareVideoEncoderFactory();
                    decoderFactory = new SoftwareVideoDecoderFactory();
                }
                //final AudioDeviceModule adm = createJavaAudioDevice();

                factory = PeerConnectionFactory.builder()
                        .setOptions(options)
                        .setVideoEncoderFactory(encoderFactory)
                        .setVideoDecoderFactory(decoderFactory)
                        .createPeerConnectionFactory();
                System.err.println("Peer connection factory created. " + factory);
                //adm.release();

                Random rand = new Random();
                userName = "android-" + rand.nextInt(10000);
                String coturnUserName = "webrtciimuserzxzq";
                String password = "zxzq852258";

                iceServers.add(PeerConnection.IceServer.builder("turn:" + host + ":3478?transport=udp")
                        .setUsername(coturnUserName)
                        .setPassword(password)
                        .createIceServer());
                iceServers.add(PeerConnection.IceServer.builder("turn:" + host + ":3478?transport=tcp")
                        .setUsername(coturnUserName)
                        .setPassword(password)
                        .createIceServer());
                iceServers.add(PeerConnection.IceServer.builder("turn:" + host + ":5349?transport=udp")
                        .setUsername(coturnUserName)
                        .setPassword(password)
                        .createIceServer());
                iceServers.add(PeerConnection.IceServer.builder("turn:" + host + ":5349?transport=tcp")
                        .setUsername(coturnUserName)
                        .setPassword(password)
                        .createIceServer());
            }
        });
        create_websocket(socketAddress);
    }

    public void unInit() {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                loginInSuccess = false;
                if(websocketClient != null) {
                    websocketClient.close();
                    websocketClient = null;
                }
                cleanFileTransformParameter();
                if (pc != null) {
                    pc.dispose();
                    pc = null;
                }
                if (factory != null) {
                    factory.dispose();
                    factory = null;
                }
                iceServers = null;
            }
        });
    }

    public void startWebrtc(String peerName) {
        if(createWebrtcCalled) {
            return;
        }
        this.peerName = peerName;

        executor.execute(() -> {
            PeerConnection.RTCConfiguration rtcConfig =
                    new PeerConnection.RTCConfiguration(iceServers);
            // TCP candidates are only useful when connecting to a server that supports
            // ICE-TCP.
            rtcConfig.tcpCandidatePolicy = PeerConnection.TcpCandidatePolicy.DISABLED;
            rtcConfig.bundlePolicy = PeerConnection.BundlePolicy.MAXBUNDLE;
            rtcConfig.rtcpMuxPolicy = PeerConnection.RtcpMuxPolicy.REQUIRE;
            rtcConfig.continualGatheringPolicy =
                    PeerConnection.ContinualGatheringPolicy.GATHER_CONTINUALLY;
            // Use ECDSA encryption.
            rtcConfig.keyType = PeerConnection.KeyType.ECDSA;
            // Enable DTLS for normal calls and disable for loopback calls.
            rtcConfig.enableDtlsSrtp = !peerConnectionParameters.loopback;
            rtcConfig.sdpSemantics = PeerConnection.SdpSemantics.UNIFIED_PLAN;

            pc = factory.createPeerConnection(rtcConfig, peer);
            System.err.println("startWebrtc pc " + pc + " factory " + factory + " peer " + peer);
            if(pc == null) {
                return;
            }

            JSONObject payload = new JSONObject();
            sendMessage("query_robot", payload);
            createWebrtcCalled = true;
        });

    }

    public void stopWebrtc() {
        if(!createWebrtcCalled) { return; }
        createWebrtcCalled = false;

        executor.execute(() -> {
            JSONObject payload = new JSONObject();
            sendMessage("leave", payload);
            if (pc != null) {
                pc.dispose();
                pc = null;
                Log.e(TAG, "pc set null");
                pc.dispose();
            }
        });
    }

    private void saveData(ByteBuffer data) {
        try {
            //Log.e(TAG, "saveData:downloadFileSizeCurrent " + downloadFileSizeCurrent);
            String[] filePathSplit = downloadFileName.split("\\.");
            String[] fileNameExtend = new String[2];
            if (filePathSplit.length == 2) {
                fileNameExtend = filePathSplit;
            } else {
                fileNameExtend[0] = downloadFileName;
                fileNameExtend[1] = "";
            }
            File file;
            if (downloadFileSizeCurrent == 0) {
                uploadRealFileSavePath = downloadSavePath + downloadFileName;
                file = new File(uploadRealFileSavePath);
                int i = 0;
                while (file.exists()){
                    uploadRealFileSavePath =
                            downloadSavePath + fileNameExtend[0] + "_" + i + "." + fileNameExtend[1];
                    file = new File(uploadRealFileSavePath);
                    i++;
                }
                file.createNewFile();
                Log.e(TAG, "uploadRealFileSavePath: " + uploadRealFileSavePath);
            } else {
                Log.e(TAG, "uploadRealFileSavePath: " + uploadRealFileSavePath);
                file = new File(uploadRealFileSavePath);
            }
            data.flip();
            FileOutputStream fileOutputStream = new FileOutputStream(file, true);
            FileChannel channel = fileOutputStream.getChannel();
            channel.write(data);
            fileOutputStream.close();

            downloadFileSizeCurrent += data.limit();
            Log.e(TAG, "saveData:downloadFileSizeCurrent " + downloadFileSizeCurrent);
            if (downloadFileSizeCurrent >= downloadFileSize) {
                JSONObject payload = new JSONObject();
                sendMessage("save-data-over", payload);
                cleanFileTransformParameter();
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void sendData() {
        try {
            Log.e(TAG, "sendData" + uploadFilePath + ": " + uploadFileName + ": " +uploadFileSize);
            RandomAccessFile file = new RandomAccessFile(uploadFilePath + uploadFileName, "r");
            FileChannel inChannel = file.getChannel();
            int chunkSize = 1024 * 16;
            int chunkNum = 0;
            ByteBuffer buffer = ByteBuffer.allocate(chunkSize);
            while(inChannel.read(buffer) > 0) {
                buffer.flip();
                DataChannel.Buffer data = new DataChannel.Buffer(buffer, true);
                if(!isUploadFile) {
                    break;
                }
                while(dataChannelLocal.bufferedAmount() > chunkSize * 300) {
                    Thread.sleep(1000);
                }
                boolean sendResult = dataChannelLocal.send(data);
                Log.e(TAG, "sendData" + sendResult);
                buffer.clear();
                if((chunkNum % 5 == 0) ) {
                    Log.e(TAG, "sleep: " );
                    Thread.sleep(1000);
                }
                chunkNum += 1;
            }
            inChannel.close();
            while(!uploadFileAccepted) {Thread.sleep(500);}
            cleanFileTransformParameter();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }  catch (InterruptedException e) {
            e.printStackTrace();
        }
        Log.e(TAG, "sendData" + "over");
    }
}
